package top.superflow.flow.config.parse.impl

import org.apache.commons.lang3.builder.ToStringBuilder
import top.superflow.core.utils.{IdentifiableUtils, PojoUtils}
import top.superflow.exception.{Exceptable, RequestUnsupportedException}
import top.superflow.flow.condition.ExpressionEngine
import top.superflow.flow.config.parse.Parser
import top.superflow.flow.config.{FlowConfig, Identifiable, PostNodeConfig, RouteConfig}
import top.superflow.flow.runtime.cache.FlowRunTimeCache
import top.superflow.flow.runtime.flow.ExtFlow
import top.superflow.flow.runtime.mapper.{AutoMatchRuntimeMapper, JsonMappingRuntimeMapper, MapperConstant, RuntimeMapper}
import top.superflow.flow.runtime.process.{ExtProcess, ProcessType, StaticClassRouteExtProcess}
import top.superflow.flow.runtime.route.ExtRoutedNode
import top.superflow.lang.{CollectionUtils, StringUtils}
import top.superflow.log.Loggable
import top.superflow.utils.{ClassUtils, MetadataUtils}

import scala.collection.mutable
import scala.collection.mutable.Set
import scala.reflect.runtime.universe._

class FlowConfigParser extends Parser[ExtFlow] with Loggable with Exceptable{


  def parse(original: Any): ExtFlow = {
    var extFlow: ExtFlow = null
    original match {
      case flowConfig: FlowConfig => {
        extFlow = new ExtFlow()
        IdentifiableUtils.copy(flowConfig, extFlow)

        extFlow.setRoutes(convertRoute(flowConfig))

        validateRoutes(extFlow)

        extFlow.setStart(flowConfig.getStart)
      }
      case _ => {
        throw new RequestUnsupportedException("Only type FlowConfig is supported, please sure pass the correct type.")
      }
    }
    extFlow
  }

  private def validateRoutes(extFlow: ExtFlow): Unit ={
    if(extFlow == null){
      return;
    }

    if(CollectionUtils.isEmpty(extFlow.getRoutes)){
      return;
    }

    for((routeKey , route) <- extFlow.getRoutes){
      validatePost(route.getPosts, extFlow.getRoutes)
    }
  }

  private def validatePost(posts : Set[ExtRoutedNode], routes : Map[String, ExtRoutedNode] ): Unit ={
    if(CollectionUtils.isEmpty(posts)){
      return;
    }

    for(post <- posts){
      val postProcessKey = post.getProcess.getKey
      if(!StringUtils.isBlank(postProcessKey) && ! routes.contains(postProcessKey)){
        throwRequestUnsupportedException("The post {} doesn't exist in the route list {} ", postProcessKey, routes)
      }
    }
  }



  private def convertRoute(flowConfig: FlowConfig) : Map[String, ExtRoutedNode] ={
    if(CollectionUtils.isEmpty(flowConfig.getRoutes)){
      throwRequestUnsupportedException("It's meaningless if there is no route in the flow {}" + ToStringBuilder.reflectionToString(flowConfig))
    }

    val extRoutes = mutable.Map[String, ExtRoutedNode]()

    for(routeConfig <- flowConfig.getRoutes){
      preProcessRoute(routeConfig)
      validateRoute(flowConfig.getKey, flowConfig.getGroup, routeConfig, extRoutes)

      val extRoute = convertRoute(flowConfig.getGroup,routeConfig)
      extRoutes += (extRoute.getKey -> extRoute)
    }

    extRoutes.toMap
  }


  private def convertRoute(currentGroup :String, routeConfig : RouteConfig) : ExtRoutedNode ={
    val extRoute = new ExtRoutedNode
    IdentifiableUtils.copy(routeConfig, extRoute)

    setProcess(currentGroup, routeConfig, extRoute)

    setPosts(currentGroup, routeConfig, extRoute)

    extRoute
  }

  private def setProcess(currentGroup: String, routeConfig: RouteConfig, extRoute: ExtRoutedNode) = {
    var process = FlowRunTimeCache.getInstance.getValid[ExtProcess](routeConfig.getRoute, currentGroup, classOf[ExtProcess])

    process = cloneProcessIfConditionRoute(process, extRoute, routeConfig)

    mergeMetadata(extRoute, process)

    extRoute.setProcess(process)
  }

  private def mergeMetadata(from : Identifiable, to : Identifiable): Unit ={
    if(from == null || to == null ){
      return
    }

    to.mergeMetadata(from.getMetadata)
  }

  private def cloneProcessIfConditionRoute(process : ExtProcess, extRoute: ExtRoutedNode,  routeConfig: RouteConfig): ExtProcess ={
    if(process == null || extRoute == null){
      return process
    }

    if(process.getProcessType != ProcessType.CONDITION){
      return process
    }

    if(process.isInstanceOf[StaticClassRouteExtProcess]){
      val clonedProcess = process.asInstanceOf[StaticClassRouteExtProcess].clone()
      val conditionRoute = clonedProcess.asInstanceOf[StaticClassRouteExtProcess]
      conditionRoute.mergeMetadata(routeConfig.getMetadata)
      FlowRunTimeCache.getInstance.cache[ExtProcess](conditionRoute)
      val condition = MetadataUtils.get(process.getMetadata, MetadataUtils.META_DATA_NAME_CONDITION)
      if(condition == null){
        throwRequestUnsupportedException("There is no condition configured in {}, but it's a condition route", routeConfig)
      }
      conditionRoute.setCondition(condition.toString)
      return conditionRoute
    }

    return process
  }

  private def setPosts(currentGroup: String, routeConfig: RouteConfig, extRoute: ExtRoutedNode): Unit ={
    if(CollectionUtils.isEmpty(routeConfig.getPosts)){
      return;
    }

    val extPosts = mutable.Set[ExtRoutedNode]()
    for(postNodeConfig <- routeConfig.getPosts){
      val extPost = convertPost(currentGroup, postNodeConfig)
      extPosts += extPost
    }
    postProcess(extPosts)
    extRoute.setPosts(extPosts)
  }

  private def postProcess(posts : Set[ExtRoutedNode]): Unit ={
    if(CollectionUtils.isEmpty(posts)){
      return
    }

    var currentMainPost : ExtRoutedNode = null
    for(post <- posts){
      if(currentMainPost == null){
        currentMainPost = getMainPost(currentMainPost, post)
      }
    }

    if(currentMainPost == null){
      setDefaultMainPost(posts)
    }
  }

  private def setDefaultMainPost(posts : Set[ExtRoutedNode]): Unit ={
    if(CollectionUtils.isEmpty(posts)){
      logger.info("There is no post at all!")
    }

    if(posts.size > 1){
      throwRequestUnsupportedException("The post size is more than one, can't set the default main route path, please configure the main post properly in the configuration!")
    }

    posts.toList.apply(0).setMain(true)
  }

  private def getMainPost(existingMainPost : ExtRoutedNode, post : ExtRoutedNode) : ExtRoutedNode = {
    if(existingMainPost != null && post.isMain){
      throwRequestUnsupportedException("There is already an existing main post {}, you can't set another post {} as main!", existingMainPost, post)
    }

    if(post.isMain){
      return post
    }

    return null
  }

  private def convertPost(currentGroup: String, postNodeConfig: PostNodeConfig): ExtRoutedNode ={
    val extRoutedNode = new ExtRoutedNode


    val extProcess = FlowRunTimeCache.getInstance.getValid[ExtProcess](postNodeConfig.getRoute, currentGroup, classOf[ExtProcess])
    extRoutedNode.setProcess(extProcess)

    val mapper: RuntimeMapper = getMapper(currentGroup, postNodeConfig)
    mapper.init(extProcess.getInputSchema)
    extRoutedNode.setMapper(mapper)

    if(postNodeConfig != null){
      extRoutedNode.setMain(postNodeConfig.getMain)
      val extExpressionCondition = ExpressionEngine.getInstance.convert(postNodeConfig.getPassCondition)
      extRoutedNode.setPassCondition(extExpressionCondition)
    }

    extRoutedNode
  }


  private def getMapper(currentGroup: String, postNodeConfig: PostNodeConfig) : RuntimeMapper = {
    if(isDefaultMapper(postNodeConfig)){
      return new AutoMatchRuntimeMapper()
    }

    if(isJsonMapper(postNodeConfig)){
      return getJsonMapper(postNodeConfig)
    }

    if(isCustomMapper(postNodeConfig)){
      return PojoUtils.createInstance(postNodeConfig.getMap.toString).asInstanceOf[RuntimeMapper]
    }

    val mapper = FlowRunTimeCache.getInstance.getValid[RuntimeMapper](postNodeConfig.getMap.toString, currentGroup, classOf[RuntimeMapper])
    mapper
  }

  private def isDefaultMapper(postNodeConfig: PostNodeConfig) : Boolean = {
    if(postNodeConfig.getMap == null){
      return true
    }
    val mapStr = postNodeConfig.getMap.toString
    StringUtils.isBlank(mapStr) || StringUtils.equalsIgnoreCase(mapStr, MapperConstant.DEFAULT_AUTO_TYPE)
  }

  private def isJsonMapper(postNodeConfig: PostNodeConfig) : Boolean = {
    val map = postNodeConfig.getMap
    if(map == null){
      return false
    }
    map.isInstanceOf[Map[_,_]]
  }

  private def getJsonMapper(postNodeConfig: PostNodeConfig) : RuntimeMapper = {
    val jsonMappingMapper = new JsonMappingRuntimeMapper(postNodeConfig.getMap.asInstanceOf[Map[String, String]])


    jsonMappingMapper
  }

  private def isCustomMapper(postNodeConfig: PostNodeConfig) : Boolean = {
    if(postNodeConfig.getMap == null){
      return false
    }
    val mapStr = postNodeConfig.getMap.toString
    if(!ClassUtils.isClass(mapStr)){
      return false
    }

    val mapClass = ClassUtils.getClass(mapStr)
    if(classOf[RuntimeMapper].isAssignableFrom(mapClass)){
      return true
    }

    false
  }

  private def preProcessRoute(routeConfig: RouteConfig): Unit ={
    if(StringUtils.isBlank(routeConfig.getKey)){
      routeConfig.setKey(routeConfig.getRoute)
    }
  }

  private def validateRoute(flowKey : String , group : String, routeConfig : RouteConfig, convertedRoutes :mutable.Map[String, ExtRoutedNode]): Unit ={
    if(routeConfig == null){
      throwRequestUnsupportedException("Route configuration can't be null!")
    }

    if(StringUtils.isBlank(routeConfig.getRoute)){
      throwRequestUnsupportedException("The process can't be blank for flow {}", flowKey)
    }

    if(convertedRoutes.contains(routeConfig.getKey)){
      val existedRoute = convertedRoutes.get(routeConfig.getKey)
      throwRequestUnsupportedException("There is already a route {} with the same key {} for the flow {}", ToStringBuilder.reflectionToString(existedRoute), routeConfig.getKey,flowKey )
    }

    val process = FlowRunTimeCache.getInstance.getValid[ExtProcess](routeConfig.getRoute, group, classOf[ExtProcess])
    if (process == null){
      throwRequestUnsupportedException("The expected process {} of group {} for flow {} doesn't exist yet, please ensure it has been configured properly!", routeConfig.getRoute, group, flowKey)
    }
  }
}

object FlowConfigParser {

  def main(args: Array[String]): Unit = {
//    val extFlow = new GlobalContext with ResourceContext {
//      override def getResourcePoolManager: ResourcePoolManager = new GlobalResourcePoolManager(null)
//    }
//
//    println(extFlow.getResourcePoolManager)
  }
}